package com.eat.fish_service.netty_client;

import com.eat.fish_service.entity.Dao.IpAndPort;
import com.eat.fish_service.netty_client.handler.*;
import com.eat.fish_service.service.IpAndPortService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author CZF
 * @create 2022-03-29 17:02
 * <p>
 * netty的客户端，用户连接服务端通信
 */
@Slf4j
@Component
public class NettyClient {

    @Autowired
    IpAndPortService ipAndPortService;

    //启动netty服务端
    public void start(List<IpAndPort> ipList) {

        //客户端需要一个事件循环组
        NioEventLoopGroup group = new NioEventLoopGroup(2);

        try {
            //创建客户端启动对象
            //注意客户端使用的不是ServerBootstrap 而是 Boostrap
            Bootstrap bootstrap = new Bootstrap();

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .option(ChannelOption.TCP_NODELAY, true)//不组装多个数据包，有数据包就直接发送
                    .option(ChannelOption.SO_KEEPALIVE,true) //两小时内没有数据的通信时，TCP会自动发送一个活动探测数据报文。
                    .channel(NioSocketChannel.class) //设置客户端通道的实体类（反射）
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
//                            socketChannel.pipeline().addLast(new InHandler());//加入自己的处理器
                            socketChannel.pipeline()
                                    .addLast(new LoggingHandler())  //日志handler
                                    .addLast(new FixedLengthFrameDecoder(60)) //解决粘包半包，固定长度的协议数据包，所以用固定长度解码器handler
                                    .addLast(new MessageCodec()) //编解码器
                                    .addLast(new InHandler()); //数据处理handler
                        }
                    });

            //把客户端加入到连接管理中，便于通过接口连接鱼缸的服务端
            ManageConnections.bootstrap = bootstrap;

            //启动客户端去连接服务器端
            connet(bootstrap, ipList);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void connet(Bootstrap bootstrap, List<IpAndPort> IpAndPortList) throws InterruptedException {
        for (IpAndPort eachIpAndPort : IpAndPortList) {

            //不使用同步，因为连接多个服务端，同步会导致其他服务端连接不上
            ChannelFuture channelFuture = bootstrap.connect(eachIpAndPort.getIp(), eachIpAndPort.getPort());


            //不使用sync()同步，使用事件监听，防止其他服务端连接不上
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(channelFuture.isSuccess()){
                        //连接成功后，加入map中，便于接口操作硬件
                        ManageConnections.channelList.put(eachIpAndPort.getAquariumId(), channelFuture.channel());
                        //检查连接：
//                        log.info("连接个数："+ManageConnections.channelList.size());

                        //连接成功后，数据库表`ip_and_port`的is_connected改为1
                        eachIpAndPort.setIsConnected(1);
                        ipAndPortService.updateById(eachIpAndPort);

                    }else {
//                        log.info("连接失败信息：");
                        log.error("连接失败--->"+eachIpAndPort+":"+eachIpAndPort.getIp());
                    }
                }
            });
        }

    }

}
